﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Trentacular.SharePoint.WSPUtil;
using Trentacular.SharePoint.StsAdmin.Util;

namespace Trentacular.SharePoint.StsAdmin
{
    public class SolutionInventoryGatherer : BaseConsoleHelper
    {
        public IEnumerable<string> Filenames { get; private set; }

        public SolutionInventoryGatherer(params string[] filenames)
        {
            Filenames = filenames;
        }

        public SolutionInventoryGatherer(IEnumerable<string> filenames)
        {
            Filenames = filenames;
        }

        public DeploymentInventory GetInventory()
        {
            var inventory = new DeploymentInventory();
            foreach (var solutionName in Filenames)
            {
                var existingSolution = SPFarm.Local.GetSolutionByName(solutionName);
                if (existingSolution == null)
                {
                    Console.WriteLine(string.Format("An existing solution was not found with the name: {0}", solutionName));
                    continue;
                }

                // Take inventory of solution deployments
                var deployInfo = new SolutionDeploymentInformation
                {
                    SolutionId = existingSolution.Id,
                    Deployed = existingSolution.Deployed,
                    DeployedWebApplications = existingSolution.ContainsWebApplicationResource ?
                        existingSolution.DeployedWebApplications.Select(wa => wa.Id).ToArray() :
                        new Guid[0]
                };

                DoConsoleAction(string.Format("Extracting features in solution: {0}", solutionName), delegate()
                {
                    deployInfo.FeatureDefinitions = existingSolution.ExtractFeatureDefinitions();
                });

                inventory.Add(solutionName.ToLower(), deployInfo);
            }

            InventoryFeatureActivations(inventory);

            return inventory;
        }

        private void InventoryFeatureActivations(DeploymentInventory inventory)
        {
            var spFarm = SPFarm.Local;
            var service = spFarm.Services.GetValue<SPWebService>("");

            Console.WriteLine("Beginning scan of all web applications in the farm");
            try
            {
                var webApplications = service.WebApplications.Cast<SPWebApplication>().ToList();

                // Have to manually add the CA web application since it is not included by default
                webApplications.Add(SPAdministrationWebApplication.Local);

                foreach (var webApp in webApplications)
                {
                    Console.WriteLine(string.Format("Beginning scan of all sites in web application: {0}", webApp.GetUrl()));
                    string context = string.Format("web application: {0}", webApp.GetUrl()); 
                    try
                    {
                        foreach (SPSite unprivelegedSite in webApp.Sites)
                        {
                            try
                            {
                                context = string.Format("site: {0}", unprivelegedSite.Url);
                                using (unprivelegedSite)
                                {
                                    using (var site = (unprivelegedSite.SystemAccount == null) ? unprivelegedSite : new SPSite(unprivelegedSite.ID, unprivelegedSite.SystemAccount.UserToken))
                                    {
                                        Console.WriteLine(string.Format("Beginning scan of all webs in site: {0}", site.Url));
                                        try
                                        {
                                            foreach (SPWeb web in site.AllWebs)
                                            {
                                                context = string.Format("web: {0}", web.Url);
                                                using (web)
                                                {
                                                    DoConsoleAction(string.Format("Taking inventory of activated features in web: {0}", web.Url), delegate()
                                                    {
                                                        if (inventory.HasFeatureDefinitions(SPFeatureScope.Web))
                                                        {
                                                            var activatedWebFeatures = web.Features.Cast<SPFeature>()
                                                                .Select(f => f.DefinitionId)
                                                                .ToList();

                                                            if (activatedWebFeatures.Count > 0)
                                                            {
                                                                foreach (var deployInfo in inventory.Values)
                                                                {
                                                                    foreach (var featureDefinition in deployInfo.FeatureDefinitions)
                                                                    {
                                                                        if (featureDefinition.Scope != SPFeatureScope.Web)
                                                                            continue;

                                                                        if (!activatedWebFeatures.Contains(featureDefinition.Id))
                                                                            continue;

                                                                        // Inventory the feature activation
                                                                        deployInfo.AddWebFeatureActivation(web, featureDefinition);
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    });
                                                }
                                            }
                                        }
                                        catch (Exception e)
                                        {
                                            HandleError(e, true);
                                        }

                                        DoConsoleAction(string.Format("Taking inventory of activated features in site: {0}", site.Url), delegate()
                                        {
                                            if (inventory.HasFeatureDefinitions(SPFeatureScope.Site))
                                            {
                                                // Inventory Site Feature Activations
                                                var activatedSiteFeatures = site.Features.Cast<SPFeature>()
                                                    .Select(f => f.DefinitionId)
                                                    .ToList();

                                                if (activatedSiteFeatures.Count > 0)
                                                {
                                                    foreach (var deployInfo in inventory.Values)
                                                    {
                                                        foreach (var featureDefinition in deployInfo.FeatureDefinitions)
                                                        {
                                                            if (featureDefinition.Scope != SPFeatureScope.Site)
                                                                continue;

                                                            if (!activatedSiteFeatures.Contains(featureDefinition.Id))
                                                                continue;

                                                            // Inventory the feature activation
                                                            deployInfo.AddSiteFeatureActivation(site, featureDefinition);
                                                        }
                                                    }
                                                }
                                            }
                                        });
                                    }
                                }
                            }
                            catch (Exception e)
                            {
                                if (context.Length > 0)
                                    context = "Exception during " + context;
                                HandleError(context, e, true);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        if (context.Length > 0)
                            context = "Exception during " + context;
                        HandleError(context, e, true);
                    }

                    DoConsoleAction(string.Format("Taking inventory of activated features in web application: {0}", webApp.GetUrl()), delegate()
                    {
                        if (inventory.HasFeatureDefinitions(SPFeatureScope.WebApplication))
                        {
                            // Inventory Web Application Feature Activations
                            var activatedWebAppFeatures = webApp.Features.Cast<SPFeature>()
                                .Select(f => f.DefinitionId)
                                .ToList();

                            if (activatedWebAppFeatures.Count > 0)
                            {
                                foreach (var deployInfo in inventory.Values)
                                {
                                    foreach (var featureDefinition in deployInfo.FeatureDefinitions)
                                    {
                                        if (featureDefinition.Scope != SPFeatureScope.WebApplication)
                                            continue;

                                        if (!activatedWebAppFeatures.Contains(featureDefinition.Id))
                                            continue;

                                        // Inventory the feature activation
                                        deployInfo.AddWebApplicationFeatureActivation(webApp, featureDefinition);
                                    }
                                }
                            }
                        }
                    });
                }
            }
            catch (Exception e)
            {
                HandleError(e, true);
            }

            DoConsoleAction("Taking inventory of activated features in the farm", delegate()
            {
                if (inventory.HasFeatureDefinitions(SPFeatureScope.Farm))
                {
                    // Inventory Farm Feature Activations
                    var activatedFarmFeatures = service.Features.Cast<SPFeature>()
                        .Select(f => f.DefinitionId)
                        .ToList();

                    if (activatedFarmFeatures.Count > 0)
                    {
                        foreach (var deployInfo in inventory.Values)
                        {
                            foreach (var featureDefinition in deployInfo.FeatureDefinitions)
                            {
                                if (featureDefinition.Scope != SPFeatureScope.Farm)
                                    continue;

                                // For farm Features, we only care about features that have been deactivated,
                                // since features are activated automatically when scoped to the farm
                                if (activatedFarmFeatures.Contains(featureDefinition.Id))
                                    deployInfo.AddFarmFeatureActivation(featureDefinition);
                                else
                                    deployInfo.AddDeactivatedFarmFeature(featureDefinition);
                            }
                        }
                    }
                }
            });
        }
    }
}
